﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;

namespace PageTurn
{
    public partial class PageTurn : UserControl
    {
        Rect rectPageTurnRightCorner = new Rect();
        Rect rectPageTurnLeftCorner = new Rect();
        bool isLeftButtonDown = false;
        RectangleGeometry pageClip = new RectangleGeometry();
        PathGeometry leftCoverClip = new PathGeometry();
        PathGeometry rightCoverClip = new PathGeometry();
        Point pM, pB, pGr, pGl, pCB, pCT;
        Point pMouse;
        double BG, MG, AG, TG, gamaInDegree, tetaInDegree;
        double halfWidth;
        double pageDiagonalLength;
        GeneralTransform transform;
        //bool canTurn;
        bool canTurnLeftToRight, canTurnRightToLeft;
        TurnMode mode;
        private enum TurnMode
        {
            None,
            TurnLeftToRight,
            TurnRightToLeft
        }
        bool isStartedTransition;
        int currentPageIndex = 0;

        List<FrameworkElement> lstPageContent;
        List<WriteableBitmap> lstPageContentImage;
        public List<FrameworkElement> LstPageContent
        {
            get { return lstPageContent; }
            set
            {
                lstPageContent = value;
                lstPageContentImage = new List<WriteableBitmap>(lstPageContent.Count);
                foreach (FrameworkElement fe in lstPageContent)
                {
                    pageLeft.Child = fe;
                    lstPageContentImage.Add(new WriteableBitmap(fe, null));
                }

                currentPageIndex = 1;
                UpdatePage();
            }
        }
        public PageTurn()
        {
            InitializeComponent();

            this.MouseLeftButtonDown += new MouseButtonEventHandler(PageTurn_MouseLeftButtonDown);
            this.MouseLeftButtonUp += new MouseButtonEventHandler(PageTurn_MouseLeftButtonUp);
            this.MouseMove += new MouseEventHandler(PageTurn_MouseMove);
            CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);

            this.LayoutRoot.Width = 1000;
            this.LayoutRoot.Height = 600;
            this.halfWidth = this.LayoutRoot.Width / 2;
            this.page.Width = this.halfWidth;
            this.page.Height = this.LayoutRoot.Height;

            this.pGr = new Point(this.LayoutRoot.Width, this.LayoutRoot.Height);
            this.pGl = new Point(0, this.LayoutRoot.Height);
            this.pB = pGr;

            this.clip.Height = this.LayoutRoot.Height * 2;
            this.clip.Width = this.page.Width;
            this.clipRotate.CenterY = this.clipTranslate.Y = -(this.clip.Height - this.page.Height) / 2;

            this.pCB = new Point(halfWidth, pGr.Y);
            this.pCT = new Point(halfWidth, 0);
            this.pageDiagonalLength = LineLength(new Point(), pCB);

            this.pageClip.Rect = new Rect(0, 0, this.clip.Width, this.clip.Height);

            double cornerSize = 50;
            this.rectPageTurnLeftCorner.X = 0;
            this.rectPageTurnLeftCorner.Y = this.LayoutRoot.Height - cornerSize;
            this.rectPageTurnLeftCorner.Width = rectPageTurnLeftCorner.Height = cornerSize;

            this.rectPageTurnRightCorner.X = this.LayoutRoot.Width - cornerSize; ;
            this.rectPageTurnRightCorner.Y = this.LayoutRoot.Height - cornerSize;
            this.rectPageTurnRightCorner.Width = rectPageTurnRightCorner.Height = cornerSize;

            Canvas.SetLeft(pageRight, this.page.Width);
            Canvas.SetLeft(pageRightCover, this.page.Width);

            pageLeft.Width = this.page.Width;
            pageLeft.Height = this.page.Height;

            pageRight.Width = this.page.Width;
            pageRight.Height = this.page.Height;

            pageContainer.Width = this.page.Width;
            pageContainer.Height = this.page.Height;

            pageRightCover.Width = this.page.Width;
            pageRightCover.Height = this.page.Height;

            pageLeftCover.Width = this.page.Width;
            pageLeftCover.Height = this.page.Height;

            pageRightCover.Visibility = System.Windows.Visibility.Collapsed;
            pageLeftCover.Visibility = System.Windows.Visibility.Collapsed;
            page.Visibility = System.Windows.Visibility.Collapsed;

            PathFigure pf;
            pf = new PathFigure() { IsClosed = true };
            pf.Segments.Add(new LineSegment());
            pf.Segments.Add(new LineSegment());
            pf.Segments.Add(new LineSegment());
            leftCoverClip.Figures.Add(pf);
            pf = new PathFigure() { IsClosed = true };
            pf.Segments.Add(new LineSegment());
            pf.Segments.Add(new LineSegment());
            pf.Segments.Add(new LineSegment());
            rightCoverClip.Figures.Add(pf);
            pageLeftCover.Clip = leftCoverClip;
            pageRightCover.Clip = rightCoverClip;

#if DEBUG
            clip.Background = new SolidColorBrush(Color.FromArgb(0x77, 0xDE, 0xB8, 0x87));
            eDiagonal.Visibility = System.Windows.Visibility.Visible;
            eWidth.Visibility = System.Windows.Visibility.Visible;
            eWidth.Width = eWidth.Height = this.LayoutRoot.Width;
            Canvas.SetTop(eWidth, this.page.Height - eWidth.Height / 2);
            eDiagonal.Width = eDiagonal.Height = pageDiagonalLength * 2;
            Canvas.SetLeft(eDiagonal, (this.LayoutRoot.Width - eDiagonal.Width) / 2);
            Canvas.SetTop(eDiagonal, (-eDiagonal.Height) / 2);

            Rectangle r = new Rectangle();
            r.Width = r.Height = 10;
            r.Fill = new SolidColorBrush(Color.FromArgb(0xff, 0xff, 0x00, 0x00));
            Canvas.SetTop(r, this.page.Height - 10);
            this.page.Children.Add(r);
#else
            this.page.Clip = this.pageClip;
#endif
        }

        void UpdatePage()
        {
            int temp = currentPageIndex * 2;
            
            pageLeft.Child = lstPageContent[temp];
            pageRight.Child = lstPageContent[temp + 1];
        }
        void UpdatePageContainer()
        {
            int temp = currentPageIndex * 2;

            if (temp - 2 < 0)
            {
                canTurnLeftToRight = false;
            }
            else
            {
                pageLeftCover.Child = new Image() { Source = lstPageContentImage[temp - 2], Stretch = Stretch.Fill };
            }

            if (temp + 3 >= lstPageContent.Count)
            {
                canTurnRightToLeft = false;
            }
            else
            {
                pageRightCover.Child = new Image() { Source = lstPageContentImage[temp + 3], Stretch = Stretch.Fill };
            }

            if (mode == TurnMode.TurnLeftToRight && canTurnLeftToRight == true)
            {
                pageContainer.Child = new Image() { Source = lstPageContentImage[currentPageIndex * 2 - 1], Stretch = Stretch.Fill };
            }
            else if (mode == TurnMode.TurnRightToLeft && canTurnRightToLeft == true)
            {
                pageContainer.Child = new Image() { Source = lstPageContentImage[currentPageIndex * 2 + 2], Stretch = Stretch.Fill };
            }
        }

        void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            if (Math.Abs(this.pMouse.X - this.pM.X) < 0.1 && Math.Abs(this.pMouse.Y - this.pM.Y) < 0.1)
            {
                canTurnLeftToRight = canTurnRightToLeft = true;
                if (currentPageIndex == 0)
                    canTurnLeftToRight = false;
                if (lstPageContent != null && (currentPageIndex + 1) * 2 >= lstPageContent.Count)
                    canTurnRightToLeft = false;
                if (isStartedTransition == true)
                {
                    pageLeftCover.Visibility = System.Windows.Visibility.Collapsed;
                    pageRightCover.Visibility = System.Windows.Visibility.Collapsed;
                    UpdatePage();
                    mode = TurnMode.None;
                    isStartedTransition = false;
                }
                return;
            }
            if (mode == TurnMode.TurnLeftToRight)
                TurnLeftToRight();
            else if (mode == TurnMode.TurnRightToLeft)
                TurnRightToLeft();
        }

        void TurnRightToLeft()
        {
            this.page.RenderTransformOrigin = new Point(0, 1);
            this.clip.RenderTransformOrigin = new Point(1, 1);

            this.pM.X += (this.pMouse.X - this.pM.X) * 0.12;
            this.pM.Y += (this.pMouse.Y - this.pM.Y) * 0.12;
            //check page With constraint
            double len = LineLength(pCB, pM);
            if (len > halfWidth)
                this.pM = ResizeLine(pCB, this.pM, halfWidth);
            //check page Diagonal constraint
            len = LineLength(new Point(halfWidth, 0), pM);
            if (len > pageDiagonalLength)
                this.pM = ResizeLine(pCT, this.pM, pageDiagonalLength);

            this.pB.X = this.pM.X;

            this.BG = LineLength(pB, pGr);
            this.MG = LineLength(pM, pGr);
            double cosGama = BG / MG;
            this.AG = this.MG / 2;
            this.TG = this.AG / cosGama;
            double gama = Math.Acos(cosGama);

            this.gamaInDegree = (this.pM.Y < this.pGr.Y) ? RadianToDegree(gama) : -RadianToDegree(gama);
            this.tetaInDegree = this.gamaInDegree * 2;

            //if (double.IsNaN(this.tetaInDegree) == true)
            //    return;

            this.pageRotate.Angle = this.tetaInDegree;
            this.pageTranslate.X = this.pM.X;
            this.pageTranslate.Y = this.pM.Y - this.pGr.Y;

            this.clipRotate.Angle = this.gamaInDegree;
            this.clipTranslate.X = this.halfWidth - this.TG;

            this.transform = this.clip.TransformToVisual(this.page);
            this.pageClip.Transform = this.transform as Transform;

            this.transform = this.clip.TransformToVisual(this.pageRightCover);
            this.rightCoverClip.Figures[0].StartPoint = new Point(this.page.Width, 0);
            (this.rightCoverClip.Figures[0].Segments[0] as LineSegment).Point = new Point(this.page.Width, this.page.Height);
            (this.rightCoverClip.Figures[0].Segments[1] as LineSegment).Point = this.transform.Transform(new Point(this.clip.Width, this.clip.Height));
            (this.rightCoverClip.Figures[0].Segments[2] as LineSegment).Point = this.transform.Transform(new Point(this.clip.Width, 0));
        }

        void TurnLeftToRight()
        {
            this.page.RenderTransformOrigin = new Point(1, 1);
            this.clip.RenderTransformOrigin = new Point(0, 1);

            this.pM.X += (this.pMouse.X - this.pM.X) * 0.12;
            this.pM.Y += (this.pMouse.Y - this.pM.Y) * 0.12;
            //check page With
            double len = LineLength(pCB, pM);
            if (len > halfWidth)
                this.pM = ResizeLine(pCB, this.pM, halfWidth);
            //check page Diagonal
            len = LineLength(new Point(halfWidth, 0), pM);
            if (len > pageDiagonalLength)
                this.pM = ResizeLine(pCT, this.pM, pageDiagonalLength);

            this.pB.X = this.pM.X;

            this.BG = LineLength(pB, pGl);
            this.MG = LineLength(pM, pGl);
            double cosGama = BG / MG;
            this.AG = this.MG / 2;
            this.TG = this.AG / cosGama;
            double gama = Math.Acos(cosGama);

            this.gamaInDegree = (this.pM.Y < this.pGr.Y) ? -RadianToDegree(gama) : RadianToDegree(gama);
            this.tetaInDegree = this.gamaInDegree * 2;

            //if (double.IsNaN(this.tetaInDegree) == true)
            //    return;

            this.pageRotate.Angle = this.tetaInDegree;
            this.pageTranslate.X = this.pM.X - this.page.Width;
            this.pageTranslate.Y = this.pM.Y - this.pGl.Y;

            this.clipRotate.Angle = this.gamaInDegree;
            this.clipTranslate.X = this.TG;

            this.transform = this.clip.TransformToVisual(this.page);
            this.pageClip.Transform = this.transform as Transform;

            this.transform = this.clip.TransformToVisual(this.pageLeftCover);
            this.leftCoverClip.Figures[0].StartPoint = new Point(0, 0);
            (this.leftCoverClip.Figures[0].Segments[0] as LineSegment).Point = new Point(0, this.page.Height);
            (this.leftCoverClip.Figures[0].Segments[1] as LineSegment).Point = this.transform.Transform(new Point(0, this.clip.Height));
            (this.leftCoverClip.Figures[0].Segments[2] as LineSegment).Point = this.transform.Transform(new Point(0, 0));
        }

        public void Next()
        {
            if (canTurnRightToLeft == false)
                return;

            mode = TurnMode.TurnRightToLeft;
            UpdatePageContainer();
            canTurnLeftToRight = canTurnRightToLeft = false;
            pageRightCover.Visibility = System.Windows.Visibility.Visible;
            page.Visibility = System.Windows.Visibility.Visible;
            this.pM = new Point(this.LayoutRoot.Width, this.LayoutRoot.Height);
            this.pMouse = new Point(0, this.LayoutRoot.Height);
            currentPageIndex++;
            isStartedTransition = true;
        }

        public void Prev()
        {
            if (canTurnLeftToRight == false)
                return;

            mode = TurnMode.TurnLeftToRight;
            UpdatePageContainer();
            canTurnLeftToRight = canTurnRightToLeft = false;
            pageLeftCover.Visibility = System.Windows.Visibility.Visible;
            page.Visibility = System.Windows.Visibility.Visible;
            this.pM = new Point(0, this.LayoutRoot.Height);
            this.pMouse = new Point(this.LayoutRoot.Width, this.LayoutRoot.Height);
            currentPageIndex--;
            isStartedTransition = true;
        }

        #region utilities method
        double RadianToDegree(double d)
        {
            return (d * 180 / Math.PI);
        }

        Point ResizeLine(Point A, Point B, double AK)
        {
            double AB = LineLength(A, B);
            if (AB == 0)
                return new Point();
            double k = AK / AB;
            Point result = new Point();
            result.X = A.X + k * (B.X - A.X);
            result.Y = A.Y + k * (B.Y - A.Y);
            return result;
        }

        double LineLength(Point p1, Point p2)
        {
            return Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y));
        }
        #endregion

        #region event
        void PageTurn_MouseMove(object sender, MouseEventArgs e)
        {
            if (canTurnLeftToRight == false && canTurnRightToLeft == false)
                return;

            Point pTemp = e.GetPosition(this.LayoutRoot);

            if (this.isLeftButtonDown == true)
            {
                this.pMouse = pTemp;
                return;
            }
            if (canTurnLeftToRight == true && rectPageTurnLeftCorner.Contains(pTemp) == true)
            {
                mode = TurnMode.TurnLeftToRight;
                pageLeftCover.Visibility = System.Windows.Visibility.Visible;
                page.Visibility = System.Windows.Visibility.Visible;
                UpdatePageContainer();
                this.pM = new Point(pTemp.X - 1, pTemp.Y - 1);
                this.pMouse = pTemp;
                return;
            }
            if (canTurnRightToLeft == true && rectPageTurnRightCorner.Contains(pTemp) == true)
            {
                mode = TurnMode.TurnRightToLeft;
                pageRightCover.Visibility = System.Windows.Visibility.Visible;
                page.Visibility = System.Windows.Visibility.Visible;
                UpdatePageContainer();
                this.pM = new Point(pTemp.X + 1, pTemp.Y + 1);
                this.pMouse = pTemp;
                return;
            }

            if (mode == TurnMode.None)
                return;

            canTurnLeftToRight = canTurnRightToLeft = false;
            if (mode == TurnMode.TurnRightToLeft)
                this.pMouse = new Point(this.LayoutRoot.Width, this.LayoutRoot.Height);
            else if (mode == TurnMode.TurnLeftToRight)
                this.pMouse = new Point(0, this.LayoutRoot.Height);
        }

        void PageTurn_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            this.isLeftButtonDown = false;
            this.ReleaseMouseCapture();

            canTurnLeftToRight = canTurnRightToLeft = false;
            if (mode == TurnMode.TurnLeftToRight)
            {
                this.pMouse = new Point(this.LayoutRoot.Width, this.LayoutRoot.Height);
                currentPageIndex--;
                isStartedTransition = true;
            }
            else if (mode == TurnMode.TurnRightToLeft)
            {
                this.pMouse = new Point(0, this.LayoutRoot.Height);
                currentPageIndex++;
                isStartedTransition = true;
            }
        }

        void PageTurn_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (canTurnLeftToRight == false && canTurnRightToLeft == false)
                return;

            Point pTemp = e.GetPosition(this.LayoutRoot);
            if (canTurnLeftToRight == true && rectPageTurnLeftCorner.Contains(pTemp) == true)
            {
                mode = TurnMode.TurnLeftToRight;
                pageLeftCover.Visibility = System.Windows.Visibility.Visible;
                page.Visibility = System.Windows.Visibility.Visible;
                UpdatePageContainer();
                this.pM = new Point(pTemp.X - 1, pTemp.Y - 1);
                this.pMouse = pTemp;
                this.isLeftButtonDown = true;
                this.CaptureMouse();
                return;
            }
            if (canTurnRightToLeft == true && rectPageTurnRightCorner.Contains(pTemp) == true)
            {
                mode = TurnMode.TurnRightToLeft;
                pageRightCover.Visibility = System.Windows.Visibility.Visible;
                page.Visibility = System.Windows.Visibility.Visible;
                UpdatePageContainer();
                this.pM = new Point(pTemp.X + 1, pTemp.Y + 1);
                this.pMouse = pTemp;
                this.isLeftButtonDown = true;
                this.CaptureMouse();
                return;
            }
        }
        #endregion
    }
}
